/** This file is part of jsMotif

Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).*
All rights reserved.

Contact:  Nokia Corporation marek.krysiuk@nokia.com

You may use this file under the terms of the BSD license as follows:

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var jsMotif = jsMotif || {};/**
 * jsMotif cross-browser dom enabler for document manipulation and events used by <code>{@link jsMotif.Template}</code>.
 * 
 * @namespace
 * @publish
 */
jsMotif.dom = (function () {
    /* Local reference to document, because accessing a local variable is faster then a global one */
    var _D = document;

    function byId (id) {
        return _D.getElementById(id);
    }

    /**
     * Always returns a DOM element - if an id is passed as a parameter it lookups an element
     * with that id otherwise it returns the element passed as the parameter. This is a helper function
     * for writing functions that accept both element ids and element objects.
     *
     * @private
     * @param {Object} el a DOM element or a element's id
     * @param {Object} a DOM element or null if the requested element wasn't found
     */
    function _elem (el) {
        return typeof el === 'string' ? byId(el) : el;
    }

    function byTagName (tagName,/*?*/ startElem) {
        return (startElem || _D).getElementsByTagName(tagName);
    }

    function extendNodeAttributes (node, attributes) {
        var attr;
        for (attr in attributes) {
            if (attributes.hasOwnProperty(attr)) {
                if (attr === 'class') {
                    node.className = attributes[attr];
                }else{
                    node[attr] = attributes[attr];
                }
            }
        }
        return node;
    }
    
    function remove (elem) {
        var node = _elem(elem);
        if(node && node.parentNode){
            node.parentNode.removeChild(node);
        }
    }

    function create(tag, /*?*/params, /*?*/body) {
        var node = _D.createElement(tag);
        node = extendNodeAttributes(node, params);
        if (body && typeof body === 'object' && body.appendChild) {
            node.appendChild(body);
        } else if (typeof body === 'string') {
            node.appendChild(_D.createTextNode(body));
        }
        return node;
    }

    function createFragment () {
        return _D.createDocumentFragment();
    }

    function append (elem, childNode) {
        var parentNode = _elem(elem);
        if (parentNode) {
            parentNode.appendChild(childNode);    
        }
    }
    
    /**
     * Moves childs from one node to another
     * @param {Object} target node
     * @param {Object} src node
     */
    function moveChildNodes (target, src) {
        var node = src.firstChild;
        while(node){
            append(target, node);
            node = src.firstChild;
        }
    }

    /**
     * Function stores a event handler for the event so all handlers can be removed later on
     *
     * @private
     * @param {Object} node a dom element which have an event added
     * @param {String} event event name
     * @param {Function} handler event handler
     */
    function _saveHandler (node, event, handler) {
        var path = '_' + event + 'Handlers';
        if (node) {
            node[path] = node[path] || [];
            node[path].push(handler);
        }

        return true;
    }


    function getMouseRelatedTarget (event) {
        event = event || window.event;
        var relTarget = event.relatedTarget;
        if (!relTarget) {
            if (event.type === "mouseout") {
                relTarget = event.toElement;
            } else if (event.type === "mouseover") {
                relTarget = event.fromElement;
            }
        }
        return relTarget;
    }

    function isContainedWithin (parent, child) {
        while (child && child !== parent) {
            child = child.parentNode;
        }
        return child === parent;
    }

    function _emulateMouseEnterLeave (element, handler) {
        return function (e) {
                var target = getMouseRelatedTarget(e);
                if (!isContainedWithin(element, target)) {
                    handler(e);
                }
        };
    }

    function addEvent (elem, event, handler, capture) {
        var node = _elem(elem),
            fn;

        if (!node) {
            return;
        }
        if (node.addEventListener) {
            fn = function (elem, event, handler, capture) {
                var node = _elem(elem);
                if (node) {
                    var isCaptured = typeof capture !== 'undefined' ? capture : false;
                    if (event === 'mouseenter' || event === 'mouseleave') {
                        var nativeEvent = event === 'mouseenter' ? 'mouseover' : 'mouseout';

                        handler = _emulateMouseEnterLeave(node, handler);
                        node.addEventListener(nativeEvent, handler, isCaptured);
                        _saveHandler(node, event, handler);

                    } else {

                        _saveHandler(node, event, handler);
                        node.addEventListener(event, handler, isCaptured);
                        //FF registers the mouse wheel event in a different way than the rest
                        if (event === 'mousewheel') {
                            node.addEventListener("DOMMouseScroll", handler, isCaptured);
                        }

                    }
                }
                return handler;
            };
        } else if (node.attachEvent) {
            fn = function (elem, event, handler) {
                var node = _elem(elem);
                if(node){
                    var newHandler = function() {
                        handler.call(elem, window.event, arguments);
                    };
                    _saveHandler(node,event,newHandler);
                    node.attachEvent('on' + event, newHandler);
                    return newHandler;
                }
            };
        }

        if (fn) {
            jsMotif.dom.addEvent = fn;
            return fn(node, event, handler, capture);
        }
    }

    function hide () {
        var node,
            dispStyle,
            alen = arguments.length;

        while (alen--) {
            node = _elem(arguments[alen]);
            if (node) {
                dispStyle = node.style.display;
                node._lastDisplay = (dispStyle !== "none") ? dispStyle : "";
                node.style.display = "none";
            }
        }
    }


    function show () {
        var node,
            alen = arguments.length;

        while (alen--) {
            node = _elem(arguments[alen]);
            if (node) {
                node.style.display = node._lastDisplay || "";
            }
        }
    }

    function stopPropagation (e) {
        var event = e || window.event;
        event.cancelBubble = true;
        if(event.stopPropagation){
            event.stopPropagation();
        }
    }

    function getEventSource (e) {
        var event = e || window.event;
        return event.target || event.srcElement;
    }

    function preventDefault (e) {
        var event = e || window.event;
        event.returnValue = false;
        if (event.preventDefault) {
            event.preventDefault();
        }
    }

    /** @lends jsMotif.dom*/
    return {
        /**
         * Retrieves the DOM element with the given id.
         *
         * @function
         * @publish
         * 
         * @param {String} id id of the requested element
         * @return {Object} a DOM node or null if the requested element wasn't found
         */
        byId: byId,

        /**
         * Creates a DOM element object based on input parameters.
         *
         * @function
         * @publish
         *
         * @param tag {String} tag name of the element
         * @param params {Object} a javascript object with properties which describe element attributes; optional
         * @param body {String|Object} a body of the element (text or DOM element); optional
         * @return {Object} created element
         */
        create: create,

        /**
         * Creates a document fragment.
         *
         * @function
         * @publish
         *
         * @return {Object} created DOM fragment
         */
        createFragment: createFragment,

        /**
         * Appends given node(s) to a parent. Can be easly combined with create function. This is a
         * variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem parent node object or its id
         * @param {Object} childNode-s an element/elements to be added in the same order as provided to arguments
         */
        append: append,

        /**
         * Registers a new event listener. Returned handler may be different than the passed one (event emulation).
         * Remember to use the returned handler for event removal. Supported event names are dom events without 'on' prefix
         * i.e <code>mouseup</code>, <code>click</code>. Plus <code>mouseenter</code> and <code>mouseleave</code> events emulation.  
         * 
         * @function
         * @publish
         *
         * @param {String|Object} elem a DOM element or its id
         * @param {String} event lower-case event name without 'on' prefix 
         * @param {Function} handler handler function
         * @param {Boolean} capture default: false
         * @return event handler, may be different than the passed one if event emulation is needed
         */
        addEvent: addEvent,

        /**
         * Hides an element. This is a variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem-s one or more DOM elements or their ids
         */
        hide: hide,

        /**
         * Shows the hidden element. This is a variable-length argument list function.
         *
         * @function
         * @publish
         *
         * @param {String|Object} elem-s one or more DOM elements or their ids
         */
        show: show,

        /**
         * Stops propagation of an event, abstract layer for handling stopping propagation in IE
         * and other browsers because they handle it differently.
         *
         * @function
         * @publish
         *
         * @param {Object} e an event object
         */
        stopPropagation: stopPropagation,

        /**
         * Extends the given node with the given attributes.
         *
         * @function
         * @publish
         *
         * @param {Object} node a DOM node
         * @param {Object} attributes a javascript object containing attributes map
         * @return {Object} extended object
         */
        extendNodeAttributes: extendNodeAttributes,

        /**
         * Checks if the child parameter is a child of the parent parameter in the DOM or if the
         * child parameter is equal to the parent parameter.
         *
         * @function
         * @publish
         *
         * @param {Object} parent a DOM object
         * @param {Object} child a DOM object
         * @return {Boolean} true if the child parameter is a child of the parent parameter
         */
        isContainedWithin: isContainedWithin,

        /**
         * Cross browser method for retrieving relatedTarget property of a mouse event
         * as returned by FF.
         *
         * @function
         * @publish
         *
         * @param {Object} event a mouse event to retrieve the relatedTarget from
         * @return {Object} relatedTarget property of the event parameter
         */
        getMouseRelatedTarget: getMouseRelatedTarget,

        /**
         * Cross browser method for retrieving event source element.
         *
         * @function
         * @publish
         *
         * @param {Object} event an event object to retrieve source element from
         * @return {Object} source element property of the event
         */
        getEventSource: getEventSource,

        /**
         * Prevents default action of an event, abstract layer for preventing default actions in IE
         * and other browsers because they handle it differently.
         *
         * @function
         * @publish
         *
         * @param {Object} e an event object
         */
        preventDefault: preventDefault,
        
        /**
         * Function checks and returns element. If dom element is provided then 
         * it returns the element if string provided it tries to fetch element by id.
         * @function
         * @publish
         * 
         * @param {String|Object} element id of dome element or element reference
         */
        elem: _elem, 
        
        /**
         * Removes Node from DOM
         * @function
         * @publish
         * 
         * @param {Object} dom node
         */
        remove: remove,
        
        moveChildNodes: moveChildNodes
    };
}());
jsMotif.comm = {};
jsMotif.comm.jsonParse = (function () {
    "use strict";

// This is a function that can parse a JSON text, producing a JavaScript
// data structure. It is a simple, recursive descent parser. It does not use
// eval or regular expressions, so it can be used as a model for implementing
// a JSON parser in other languages.

// We are defining the function inside of another function to avoid creating
// global variables.

    var at, // The index of the current character
        ch, // The current character
        escapee = {
            '"': '"',
            '\\': '\\',
            '/': '/',
            b: '\b',
            f: '\f',
            n: '\n',
            r: '\r',
            t: '\t'
        },
        text,

        error = function (m) {

// Call error when something is wrong.

            throw {
                name: 'SyntaxError',
                message: m,
                at: at,
                text: text
            };
        },

        next = function (c) {

// If a c parameter is provided, verify that it matches the current character.

            if (c && c !== ch) {
                error("Expected '" + c + "' instead of '" + ch + "'");
            }

// Get the next character. When there are no more characters,
// return the empty string.

            ch = text.charAt(at);
            at += 1;
            return ch;
        },

        number = function () {

// Parse a number value.

            var number,
                string = '';

            if (ch === '-') {
                string = '-';
                next('-');
            }
            while (ch >= '0' && ch <= '9') {
                string += ch;
                next();
            }
            if (ch === '.') {
                string += '.';
                while (next() && ch >= '0' && ch <= '9') {
                    string += ch;
                }
            }
            if (ch === 'e' || ch === 'E') {
                string += ch;
                next();
                if (ch === '-' || ch === '+') {
                    string += ch;
                    next();
                }
                while (ch >= '0' && ch <= '9') {
                    string += ch;
                    next();
                }
            }
            number = +string;
            if (!isFinite(number)) {
                error("Bad number");
            } else {
                return number;
            }
        },

        string = function () {

// Parse a string value.

            var hex,
                i,
                string = '',
                uffff;

// When parsing for string values, we must look for " and \ characters.

            if (ch === '"') {
                while (next()) {
                    if (ch === '"') {
                        next();
                        return string;
                    } else if (ch === '\\') {
                        next();
                        if (ch === 'u') {
                            uffff = 0;
                            for (i = 0; i < 4; i += 1) {
                                hex = parseInt(next(), 16);
                                if (!isFinite(hex)) {
                                    break;
                                }
                                uffff = uffff * 16 + hex;
                            }
                            string += String.fromCharCode(uffff);
                        } else if (typeof escapee[ch] === 'string') {
                            string += escapee[ch];
                        } else {
                            break;
                        }
                    } else {
                        string += ch;
                    }
                }
            }
            error("Bad string");
        },

        white = function () {

// Skip whitespace.

            while (ch && ch <= ' ') {
                next();
            }
        },

        word = function () {

// true, false, or null.

            switch (ch) {
            case 't':
                next('t');
                next('r');
                next('u');
                next('e');
                return true;
            case 'f':
                next('f');
                next('a');
                next('l');
                next('s');
                next('e');
                return false;
            case 'n':
                next('n');
                next('u');
                next('l');
                next('l');
                return null;
            }
            error("Unexpected '" + ch + "'");
        },

        value, // Place holder for the value function.

        array = function () {

// Parse an array value.

            var array = [];

            if (ch === '[') {
                next('[');
                white();
                if (ch === ']') {
                    next(']');
                    return array; // empty array
                }
                while (ch) {
                    array.push(value());
                    white();
                    if (ch === ']') {
                        next(']');
                        return array;
                    }
                    next(',');
                    white();
                }
            }
            error("Bad array");
        },

        object = function () {

// Parse an object value.

            var key,
                object = {};

            if (ch === '{') {
                next('{');
                white();
                if (ch === '}') {
                    next('}');
                    return object; // empty object
                }
                while (ch) {
                    key = string();
                    white();
                    next(':');
                    if (Object.hasOwnProperty.call(object, key)) {
                        error('Duplicate key "' + key + '"');
                    }
                    object[key] = value();
                    white();
                    if (ch === '}') {
                        next('}');
                        return object;
                    }
                    next(',');
                    white();
                }
            }
            error("Bad object");
        };

    value = function () {

// Parse a JSON value. It could be an object, an array, a string, a number,
// or a word.

        white();
        switch (ch) {
        case '{':
            return object();
        case '[':
            return array();
        case '"':
            return string();
        case '-':
            return number();
        default:
            return ch >= '0' && ch <= '9' ? number() : word();
        }
    };

// Return the json_parse function. It will have access to all of the above
// functions and variables.

    return function (source, reviver) {
        var result;

        text = source;
        at = 0;
        ch = ' ';
        result = value();
        white();
        if (ch) {
            error("Syntax error");
        }

// If there is a reviver function, we recursively walk the new structure,
// passing each name/value pair to the reviver function for possible
// transformation, starting with a temporary root object that holds the result
// in an empty key. If there is not a reviver function, we simply return the
// result.

        return typeof reviver === 'function' ? (function walk(holder, key) {
            var k, v, value = holder[key];
            if (value && typeof value === 'object') {
                for (k in value) {
                    if (Object.prototype.hasOwnProperty.call(value, k)) {
                        v = walk(value, k);
                        if (v !== undefined) {
                            value[k] = v;
                        } else {
                            delete value[k];
                        }
                    }
                }
            }
            return reviver.call(holder, key, value);
        }({'': result}, '')) : result;
    };
}());

jsMotif.comm.xhttp = (function(){
    
    var jsonParse = (window.JSON)? window.JSON.parse : jsMotif.comm.jsonParse;
    
    function createXmlHttpObject() {
        if(window.XMLHttpRequest){
            return new XMLHttpRequest();
        }else{
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }

    function getJson (url, onComplete, params) {
        params = params || {};
        
        var xmlHttp = createXmlHttpObject(),
            headers = params.headers || {},
            timeout = params.timeout || 4000;
        
        xmlHttp.timeout = timeout;
        xmlHttp.open('GET', url);
        xmlHttp.setRequestHeader('Accept','application/json');
        xmlHttp.setRequestHeader('Content-Type','application/json');
        
        if(headers){
            for(var key in headers){
                if(headers.hasOwnProperty(key)){
                    xmlHttp.setRequestHeader(key,headers[key]);
                }
            }
        }
        
        xmlHttp.ontimeout = function(){
            onComplete({}, 'timeout');
        };
        
        xmlHttp.onreadystatechange = function(){
            if(xmlHttp.readyState === 4){
                var respObject = {},
                    status = xmlHttp.status,
                    respText = xmlHttp.responseText;
                
                if(status === 200){
                    respObject = jsonParse(respText);
                }
                
                onComplete(respObject, (status === 200)? 'ok' : 'error');
            }
        };
        
        xmlHttp.send();            
    }
    
    return {
        getJson: getJson
    };
    
}());
jsMotif.comm.jsonp = (function(){
    
    var _dom = jsMotif.dom,
        _seed = 100,
        _cbfnNS= 'jsMotif.comm.jsonp',
        _defaultTimeout = 4000;
  
    function getCbId(){
        return ++_seed;
    }
  
    function get (url) {      
        var script = _dom.create('script', {
            src: url,
            type: 'text/javascript'
        });     
                
        script.onload = function(){
            _dom.remove(this);
        };
        //ie hack
        script.onreadystatechange = function () {
            if (this.readyState === 'loaded') {
                _dom.remove(this);
            }
        };      
        _dom.append(document.body, script);        
    }
    
    function removeCallBack(cbName){
        delete jsMotif.comm.jsonp[cbName];
    }
    
    function createHandler (onComplete, timeout){
        var $comm = jsMotif.comm, 
            cbId = getCbId(),
            handlerName = 'handle' + cbId,
            timeoutHandler = setTimeout(function(){
                onComplete({}, 'timeout');
                $comm.jsonp[handlerName] = function(){};
                //add 5 sec and remove callback if request eventually will come in
                setTimeout(function(){
                    removeCallBack(handlerName);
                },5000);
                
            }, timeout || _defaultTimeout);
        
        $comm.jsonp[handlerName] = function (data) {
            clearTimeout(timeoutHandler);
            onComplete(data, 'ok');
            removeCallBack(handlerName);
        };
        
        return handlerName;
    }
    
    function getJson (url, onComplete, params) {
        var timeout = (params)? params.timeout : '',
            handlerName =  createHandler(onComplete, timeout),
            requestUrl = url.replace('{CB_FN}', [_cbfnNS , handlerName].join('.'));
       
        get(requestUrl);
    }
    
    
    return {
        getJson: getJson
    };
    
}());
/**
 * Template errors and warning definitions
 * @namespace
 * @publish
 */
jsMotif.errors = {
    LVL: {
        log: 3,
        warn: 2,
        critical: 1
    },
    MSG: {
        undefData: 'Undefined data',
        undefModule: 'Undefined module',
        wrongSrcNode: 'This node type cannot have src attribute',
        reqRelNotFound: 'Cannot found required rel %s1 in %s2 module'
    }
};
/**
 * This namespace defines selector for JSON objects, which is a simplified Xpath for
 * safe JSON object data selection. The functions in this namesspace are used within 
 * <code>{@link jsMotif.Template}</code>.
 * @namespace
 * @publish
 */
jsMotif.selector = (function(){
     
     /**
      * Trim
      * @private
      * @param {Object} text
      */
     function trim (text) {
        return text.replace(/(^\s+)|(\s+$)/g, '');
     }
     
     /**
      * Unquote
      * @param {Object} text
      */
     function unquote(text){
         return text.replace(/(^\')|(^\")|(\'$)|(\"$)/g, '');
     }
    
    /**
     * Filtering operatos
     * 
     * @private
     * @param {Object} argLeft
     * @param {Object} argRight
     */
     var operators = {
         '==': function(argLeft, argRight){
             return argLeft == argRight;
         },
         '!=': function (argLeft, argRight){
             return (argLeft)? argLeft != argRight : false;
         },
         '>': function (argLeft, argRight){
             return argLeft > argRight;
         },
         '<': function (argLeft, argRight){
             return argLeft < argRight;
         },
         '>=': function (argLeft, argRight){
             return argLeft >= argRight;
         },
         '<=': function (argLeft, argRight){
             return argLeft <= argRight;
         },
         '!': function (argLeft, argRight){
             return !argLeft;
         }
     };
     
     /**
      * Function parses filter selector, to set the action 
      * function for the selector operator
      * 
      * @private
      * @param {Object} selector
      */
     function parseSelector(selector) {
         
         var selectedIndex = -1,
             selectedOperator,
             index,
             argLeft,
             argRight,
             action;
         
         if(selector[0] === '!'){
             action = operators['!'];
             argLeft = trim(selector.substr(1, selector.length));
         }else{
            for(var operator in operators){
                if (operators.hasOwnProperty(operator)) {
                    index = selector.lastIndexOf(operator);
                    if (index > selectedIndex) {
                        selectedIndex = index;
                        selectedOperator = operator;
                    }
                }
            }
            
            if (selectedIndex !== -1) {
                 action = operators[selectedOperator];
                 argRight = unquote(trim(selector.substr(selectedIndex, selector.length).split(selectedOperator)[1]));
                 argLeft = (selectedOperator !== '!')? trim(selector.substr(0, selectedIndex)) : argRight;
            }
         }

        return {
            valuePath: argLeft,
            toValue: argRight,
            action: action
        };  
          
     }
     
     
     
     
     /**
      * Function gets property for an object, 
      * property is stored as an array of characters
      * 
      * @private
      * @param {Object} obj
      * @param {Object} subPath
      */
     function getProperty(obj, subPath) {
         var property = subPath.join('');
         if(property!==''){
            obj = obj[property];
         }
         
         return obj;
     }
     
    
     /**
      * Functions for json object path tokens
      * handling. On each token action is performed
      * 
      * @private
      * @param {Object} obj
      * @param {Object} subPath
      * @param {Object} stack
      * @param {Object} token
      */
     var tokens = {
         '.': function(obj, subPath, stack, token){
             if (!stack.length) {
                 if (obj instanceof Array) {
                    for(var i=0, l=obj.length, result = []; i<l; i++){
                        result.push(getProperty(obj[i],subPath));
                    }
                    
                    obj = result;
                 }
                 else {
                     obj = getProperty(obj, subPath);
                 }
                 
                 subPath.splice(0, subPath.length);
             }else{
                 subPath.push(token);
             }
             
             return obj;
         },
         
         '(': function (obj, subPath){
             return obj;
         },
         
         ')': function (obj, subPath, stack, token) {
             if (!stack.length) {
                 var property = subPath.join('');
                 if (typeof obj[property] === 'function') {
                     return obj[property]();
                 }
                 return;
             }else{
                 subPath.push(token);
             }
             return obj;
         },
         
         '[': function (obj, subPath, stack, token){
            
             if(!stack.length){
                 obj = getProperty(obj, subPath);
                 subPath.splice(0, subPath.length);
             }else{
                 subPath.push(token);
             }
             
             stack.push(token);
             return obj;
         },
         
         ']': function (obj, subPath, stack, token) {
             stack.pop();
             if(!stack.length){
                 var property = subPath.join('');
                 if(isNaN(property)){
                     obj = getBySelector(obj, property);
                 }else{
                     if (property !== '') {
                         obj = obj[parseInt(property, 10)];
                     }
                 }
                 subPath.splice(0, subPath.length);
             }else{
                 subPath.push(token);
             }
             
             return obj;
         }
     };
   
   
     /**
      * Normalize path, normalizes path removes extra '.', and '"' and adds
      * '.' at the end of the path
      * @private
      * @param {Object} path
      */
     function normalizePath (path) {
        path = (typeof(path) === 'string')? path : '';
        path = path.replace(/([\]\)\.]+)\./g, function($0, $1){
            return $1 ? $1 + '' : $0;
        });
        
        if(!tokens[path.charAt(path.length-1)]){
            path += '.';
        }
        
        return path;
     }
    
    
    /**
     * This function retrieves data from a JSON object, using the provided path. 
     *
     * @param {Object} path The path to data within the JSON object; may contain array selectors
     * @param {Object} defaultValue The default value to return when the path finds nothing
     * 
     * @publish
     * 
     * @example 
     * getData.call(jsonObject,'path1.path2[3]','default text');
     * 
     */
    function getData(path, defaultValue) {
        var obj = this,
            subPath = [],
            stack = []; 
        
        path = normalizePath(path);
        for(var i=0, l=path.length; i<l; i++){
            var ch = path.charAt(i);
            
            if(tokens[ch]){
               obj = tokens[ch](obj, subPath, stack, ch);
            }else{
               subPath.push(ch);
            }
            
            if(!obj){
                return defaultValue;
            }
        }
        return obj;
       
    }
    
     /**
      * This function resolves a conditional path or a selector.
      * 
      * @param {Object} selector A selector or a conditional path ({String}) 
      * @return A value (data) selected by the argument or, if the argument contains a conditional 
      * path, a Boolean value indicating if the condition is met
      * @publish
      * 
      * @example 
      * resolveCondition.call(jsonObj,'path1.inner1');
      * resolveCondition.call(jsonObj,'path1.inner2=="some text");
      */
     function resolveCondition (selector){
        var obj = this,
            select,
            value,
            data = getData.call(obj, selector, false);
        
        if(data){
            return data;
        }else{
            select = parseSelector(selector);
            if(select.action){
                value = getData.call(obj, select.valuePath);
                return select.action(value, select.toValue);
            }
        }
    
     }
    
    
    /**
      * Function gets elements from the array according to 
      * selector
      * 
      * @private
      * @param {Object} obj
      * @param {Object} selector
      */
     function getBySelector (obj, selector) {
         var select = parseSelector(selector),
             results = [];
        
         for(var i=0, l=obj.length; i<l; i++){
             var arrayObj = obj[i],
                 value = getData.call(arrayObj, select.valuePath);
                 
             if(select.action(value, select.toValue)){
                 results.push(arrayObj);
             }
         }
        
         return results;
     }
    
    return {
        resolveCondition: resolveCondition,
        getData: getData
    };
    
}());
/**
 * <code>jsMotif</code> is a template rendering class extended by {@link nokia.places.APITemplate}.
 * This method creates a new instance of <code>jsMotif</code>.
 * @class
 * 
 * @publish
 * @param {Object} params An initialization object with the following properties
 * <ul>
 *      <li>
 *          <code>template</code> - (mandatory) a value which represents a template it can be:
 *          <ul>
 *              <li>a {String} containing HTML elements, e.g '&lt;div>&lt;a>&lt;/a>&lt;/div>'</li>
 *              <li>a {String} containing an existing DOM element id (the template pattern is 
 *              extracted from the DOM node</li>
 *              <li>a {Object} reference to existing DOM node in javascript</li>
 *          </ul>
 *      </li>
 *      <li>
 *          <code>targetNode</code> - the element into which the template is to be rendered; its value 
 *          can be:
 *          <ul>
 *              <li>a {String} which represents an existing element id</li>
 *              <li>an {Object} which represents a reference to an existing DOM element in JavaScript</li>
 *          </ul>
 *      </li>
 *      <li>
 *          <code>functions</code> - an object/hashmap which contains a user-defined function that
 *          may be used to render or to affect rendering of the element in template.
 *      </li>
 *      <li>
 *          <code>event</code> - an array of objects which specify the events attached to template nodes and
 *          the event handlers; each object in the array has the following attributes: 
 *          <ul>
 *              <li>
 *                  <code>rel</code> - a {String} indicating the template node to which to attach the event
 *              </li>
 *              <li>
 *                  <code>name</code> - a {String} providing the name of the attached event; the supported
 *                  event names are 'keydown', 'keyup', 'keypress', 'click', 'dblclick', 'mousedown', 
 *                  'mousemove', 'mouseout', 'mouseover', 'mouseup', 'mouseenter', 'mouseleave', 'blur', 
 *                  'change', 'focus', 'reset', 'select', 'submit', 'abort'
 *              </li>
 *              <li>
 *                  <code>handler</code> - a caller-defined {Function} to be invoked when the user 
 *                  triggers the attached event; the function receives the data object as an argument and 
 *                  'this' within the function refers to the element on which the event was triggered
 *              </li>
 *          </ul>
 *      </li>
 *  </ul>
 *  
 *  @memberOf jsMotif
 */
jsMotif.Template = (function(){
    
    var $E = jsMotif.errors,
        _dom = jsMotif.dom,
        _selector = jsMotif.selector;
        
    var _stripBrackets = function (value) {
        if (value && '{' === value.charAt(0) &&  '}' === value.charAt(value.length-1)) {
            return value.substr(1).substr(0, value.length-2);
        }
        return false;
    };
    
    /**
     * Function returns a function path within json object
     * @param {Object} path
     * 
     * @private
     */
    var getFunctionPath = function (path){
        var functionMatch = path.match(/(\S+)\(\)/);
        if(functionMatch){
            return functionMatch[1];
        }
    };

    /**
     * Filling target node with template
     * @param {Object} node
     * @param {Object} jsonObject
     * @param {Object} fragment
     * 
     * @private
     */
    var fillNode = function(node, jsonObject, fragment){
        var cloned = fragment.cloneNode(true);

        this.renderObject(cloned, jsonObject);
        _dom.append(node, cloned);
    };
    
    /**
     * Funtion calls the predefined function for provided control path 
     * @param {Object} value
     * @param {Object} controlPath
     * @param {Object} jsonObject
     * @param {Object} node
     * 
     * @private
     */
    var getFunctionValueForUndef = function (value, controlPath, jsonObject, node) {
        if(typeof(value) === 'undefined'){
            var functionPath = getFunctionPath(controlPath);
            value = (functionPath && this.predefinedFunctions[functionPath])? this.predefinedFunctions[functionPath].call(node, jsonObject) : value;
        }
        return value;
    };

    /**
     * Function returns data using attribute value and json object
     * 
     * @param {Object} node
     * @param {Object} path
     * @param {Object} jsonObject
     */
    var getAttributeData = function (node, value, jsonObject, decode) {
        
        if(decode){
            value = decodeURI(value);
        }
        
        value = _stripBrackets(value);
        if (false === value) {
            return;
        }
        
        var data = _selector.resolveCondition.call(jsonObject, value);
        data = getFunctionValueForUndef.call(this, data, value, jsonObject, node);
        return data;
        
    };

    /**
     * Function wraps getAttributeData function and allows for multiple json paths,
     * in some attributes like fill, url etc
     * @param {Object} node
     * @param {Object} value
     * @param {Object} jsonObject
     * @param {Object} decode
     */
    var parseAttrubute = function(node, value, jsonObject, decode){
        var self = this,
            result = value.replace(/(\{[^\{\}]+\})/g, function($0, closuredJsonPath){
                var data = getAttributeData.call(self, node, closuredJsonPath, jsonObject, decode);
                return (data)? data : '';
            });
            
        return result;
    };

    


    /**
     * Handler for fill attribute
     * When renderObject() finds fill attribute in node, this function is called
     * Registered using registerAttr()
     *
     * @param {Object} node
     * @param {String} value of fill attribute
     * @param {Object} jsonObject 
     */
    var onAttrFill = function(node, value, jsonObject){
        var data = parseAttrubute.call(this, node, value, jsonObject);
        if (data) {
            if (data instanceof Array) {
                data = data.join(', ');
            }
            
            node.innerHTML = data;
            return data;
        }else{
            _dom.hide(node);
            return false;    
        }        
    };

    /**
     * Helper handler for onAttrHref and onAttrSrc
     * This function is called when renderObject() finds the attribute <code>src</code>
     * in a node.
     * Registered using registerAttr()
     *
     * @param {Object} node
     * @param {String} value of src attribute
     * @param {Object} jsonObject 
     */
    var onAttrUrl = function(node, value, jsonObject, attr){
        var data = parseAttrubute.call(this, node, value, jsonObject, true),
            nodeName = node.nodeName.toLowerCase();
        if (typeof(data) === 'string') {
            if(nodeName === 'a' || nodeName === 'link'){
                node.href = data;   
            }else{
                node.src = data;
            }
        }
        return data;
    };

    /**
     * Handler for attribute mailto inserts clicable link on node with email
     * 
     * @param {Object} node
     * @param {Object} value
     * @param {Object} jsonObject
     */
    var onAttrMailto = function(node, value, jsonObject){
        var data = parseAttrubute.call(this, node, value, jsonObject, true);
        if (data) {
            node.setAttribute('href', 'mailto:'+data);
        }
        return data;
    };
    
    /**
     * Handler for attribute if hides or shows a node, depending on whether 
     * the specified condition is true or false.
     * 
     * @param {Object} node
     * @param {Object} value
     * @param {Object} jsonObject
     */
    var onAttrIf = function (node, value, jsonObject) {
        var data = getAttributeData.call(this, node, value, jsonObject);
        if(!data){
            _dom.hide(node);
            return false;
        }
    };
    
    /**
     * Handler function for each attribute repeats inner html respectively for every element of the array
     * 
     * @param {Object} node
     * @param {Object} value
     * @param {Object} jsonObject
     */
    var onAttrEach = function (node, value, jsonObject){
        var data = getAttributeData.call(this, node, value, jsonObject);
        if (data instanceof Array && data.length !== 0) {
            var tempFrag = _dom.createFragment();
            _dom.moveChildNodes(tempFrag, node);
            for(var i=0, l=data.length; i<l; i++){
                data[i]._index = (i+1);
                fillNode.call(this, node, data[i], tempFrag);
            }
        }else{
            _dom.hide(node);
        }
        
        return false;
    };

    /**
     * Handler for tpl attribute. The attribute's value is the name of a template. This can be the 
     * id of the HTML that defines the template or the name of a standard template file provided with the 
     * API, for example, "nokia.general.place", "nokia.general.address", "nokia.general.contact", etc., where
     * the last element is the base name of an HTML file.

     * When renderObject() finds tpl attribute in node, this function is called
     * Registered using registerAttr()
     *
     * @param {Object} node
     * @param {String} value of tpl attribute
     */
    var onAttrTpl = function(node, value){
        var el = _dom.byId(value);
        if(el){
             _dom.moveChildNodes(node, el.cloneNode(true));
             node.removeAttribute('tpl');
        }
    };
    
    /**
     * Handler for rel attribute. The role of the attribute is primarily to assign events to 
     * HTML elements in a template, but also to identify HTML elements as targets for modules.
     * @param {Object} node
     * @param {Object} value
     * @param {Object} jsonObject
     */
    var onAttrRel = function (node, value, jsonObject) {
        var events = this.eventMap[value],
            handler = function(event, node, jsonObject){
                return function(e){
                    event.handler.call(node, jsonObject, e);
                };
            };
            
        if (events) {
            for (var i = 0, l = events.length; i < l; i++) {
                var event = events[i];
                if (event && event.name && typeof(event.handler) === 'function') {
                    _dom.addEvent(node, event.name, handler(event, node, jsonObject));
                }
            }
        }
    };
    
    /**
     * Function initializes the jsMotif object (creates template pattern)
     * @private
     */
    var initialize = function(){
        var tempElement = _dom.create('div',{});
        tempElement.innerHTML = this.templateBody;
        this.fragment = _dom.createFragment();
        _dom.moveChildNodes(this.fragment, tempElement);
    };
    
    var jsMotifClass = function (params) {
        this.targetElem = _dom.elem(params.targetNode) || _dom.elem(params.template);
        
        this.mapping = [];
        var templateHolder = _dom.elem(params.template);
        if(templateHolder){
            this.templateBody = templateHolder.innerHTML;    
        }else{
            this.templateBody = params.template;
        }

        this.debug = params.debug || 0;
        this.predefinedFunctions = params.functions || {};
        this.attributes = [];
        
        this.registerAttr('tpl', onAttrTpl);
        this.registerAttr('if', onAttrIf);
        this.registerAttr('each', onAttrEach);
        this.registerAttr('fill', onAttrFill);
        this.registerAttr('url', onAttrUrl);
        this.registerAttr('rel', onAttrRel);
        this.registerAttr('mailto', onAttrMailto);
        
        this.eventMap = {};
        this.addEventMap(params.events);
        
        initialize.call(this);
    };

    /**
     * Function collects the rel attributes and stores to 'this' context
     * since it works recursively for child nodes use it within result object context 
     * @example
     * var relMap = {}; 
     * getRel.call(relMap, node);
     * 
     * @param {Object} node
     */
    var getRel = function (node) {
        var childNodes = node.childNodes,
            child,
            rel;
        
        for(var i=0, l=childNodes.length; i<l; i++){
            child = childNodes[i];
            if(child.nodeName !== '#text' && child.nodeName !== '#comment'){
                rel = child.getAttribute('rel');
                if(rel){
                    this[rel] = child;
                }
                if (child.hasChildNodes()) {
                    getRel.call(this, child);
                }
            }        
        }
    };
    
    jsMotifClass.prototype = {

        /**
         * This method renders the HTML template, using the data and the target element 
         * specified by the caller. 
         * @param {Object} jsonObject An object containing the data to be rendered
         * @param {Object} targetElem An optional target element in which to render the data; it 
         * is specified as {String) containing the DOM id or a node reference
		 * @param {Boolean} append An flag which tells if the template should append rendered element to target Node
		 * without clearing the node content
         * @publish
         */
        render: function (jsonObject, targetElem /*?*/, append /*?*/) {
            this.jsonObject = jsonObject;
            var targetNode = (targetElem)? _dom.elem(targetElem) : this.targetElem;
            if (targetNode) {
				if(!append){
					targetNode.innerHTML = '';
                }
				fillNode.call(this, targetNode , jsonObject, this.fragment);
                this.targetElem = targetNode;
            }else{
                this.errorLog($E.LVL.critical, $E.MSG.noTargetNode, this);
            }
            
        },


        /**
         * This method renders the received data using the HTML template and returns the 
         * result as an HTML string. No events are attached.
         * @param {Object} jsonObject  An object containing the data to be rendered
         * @reutrn An HTML string into which the received data have been rendered 
         * @publish
         */
        fetch: function (jsonObject) {
            this.jsonObject = jsonObject;
            
            var tempElement = _dom.create('div',{});
            fillNode.call(this, tempElement, jsonObject, this.fragment);

            return tempElement.innerHTML;
        },

        /**
         * This method registers a handler for an attribute in a template DOM node.
         * @param {String} name The name of the attribute as a string
         * @param {Function} handler A caller-defined attribute handler function 
         * @publish
         */
        registerAttr: function(name, handler){
            this.attributes.push({name: name, handler: handler});
        },
        
        /**
         * This method adds an event handler to a template element.
         * @param {Object} rel The value of the <code>rel</code> attribute in the template node
         * @param {Object} name The name of the event, for example 'click', 'mouseout', etc.
         * @param {Object} handler An event handler function defined by the caller
         * @publish
         */
        addEvent: function(rel, name, handler) {
            if(rel && name && handler){
                if(!this.eventMap[rel]){
                    this.eventMap[rel] = [];
                }
                
                this.eventMap[rel].push({
                    name: name, 
                    handler: handler
                });    
            }
        },
        
        /**
         * This method adds an event map to the template.
         * @param {Object} events An array object assigning events and event handlers to a template node 
         * (see also the parameter <code>event</code> in the class contructor)
         * @publish
         */
        addEventMap: function(events){
            if(events && (events instanceof Array)){
                for(var i=0, l=events.length; i<l; i++){
                    var event = events[i];
                    this.addEvent(event.rel, event.name, event.handler);    
                }    
            }
                
        },
        
        /**
         * This method obtains a map object relating the <code>rel</code> attributes and the
         * DOM elements to which they apply within the caller-supplied DOM node.
         * 
         * @param {Object} node The DOM element in the template; all children of this element 
         * are checked for the <code>rel</code> attribute
         * @return An {Object} mapping the <code>rel</code> attributes and DOM elements
         * @publish
         */
        getRelMap: function (node) {
            var relMap = {};
            getRel.call(relMap, node);        
            return relMap;
        },

        /**
         * Renders single node
         * @param {Object} node
         * @param {Object} jsonObject - data needed to render node
         */
        renderObject: function (node, jsonObject) {
            var childNodes = node.childNodes,
                i=0,
                l=0,
                value,
                attribute,
                attributeName,
                processChildren;

            if (node.getAttribute) {
                for (i = 0, l = this.attributes.length; i < l; i++) {
                    attribute = this.attributes[i];
                    attributeName = attribute.name;
                    value = node.getAttribute(attributeName);
                    if(value){
                        /**
                         * If handler will return false, we shouldn't process children, 
                         * one false should stop all inner processing!
                         */
                        var handlerResult = attribute.handler.call(this, node, value, jsonObject);
                        if(processChildren !== false){
                            processChildren = handlerResult;
                        }
                        
                        
                        if (attributeName !== 'rel' && attributeName !== 'tpl' && attributeName !== 'src' && attributeName !== 'href') {
                            node.removeAttribute(attributeName);
                        }
                    }
                }
            }
           
            if(false === processChildren){
                return false;
            }

            for(i=0, l=childNodes.length; i<l; i++){
                var child = childNodes[i];
                if (child.nodeName !== '#text' && child.nodeName !== '#comment') {
                    this.renderObject(child, jsonObject);
                }
            }
        },
        
        /**
         * Function logs any errors related to wrong usage
         * @param {Object} level
         * @param {Object} params
         */
        errorLog: function(level, params/*, ...*/){
            var args = Array.prototype.slice.call(arguments, 1);
            
            if(this.debug >= level && 'object' === typeof(window.console)){
                if(level === $E.LVL.log){
                    return window.console.log.apply(window.console, args);
                }
                if(level === $E.LVL.warn){
                    return window.console.warn.apply(window.console, args);
                }
                if(level === $E.LVL.critical){
                    return window.console.error.apply(window.console, args);
                }
            }
        }


    };
    
    return jsMotifClass;
    
}());
jsMotif.Template.prototype.renderJSON = function(url, params, targetElem){  
    var self = this,
        $comm = jsMotif.comm,
        jsonFetcher = (params && params.reqType === 'jsonp')? $comm.jsonp : $comm.xhttp;
    
    if(typeof(url) === 'string'){
        
        jsonFetcher.getJson(url, function(data, status){
            if(params && params.onData){
                params.onData(data, status);
            }
            self.render(data, targetElem);
        }, params);
        
    }else{
        self.render(url, targetElem);   
    }
};
